﻿using log4net;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using LinqToTwitter;

namespace Version2
{
    [System.Runtime.InteropServices.ComVisible(true)]
    public partial class Form1 : Form
    {

        private ILog logger = LogManager.GetLogger(typeof(Form1));
        private List<SocialDataPoint> theData = new List<SocialDataPoint>();
        private Double[] lastLatLon = null;
        private List<SearchParameters> previousSearchList = new List<SearchParameters>();
        private Boolean autoSearchOn = false;
        private int autoSearchInterval = 30;
        private Double[] tacomaLatLon = new Double[] { 47.2551307678223, -122.441650390625 };
        private TwitterUtil tUt = new TwitterUtil();
        private Boolean streamSeattleTweetByBeat = false;
        private SeattlePDTweetByBeat spdTweets = null;

        public Form1()
        {
            InitializeComponent();

            webBrowser1.ObjectForScripting = this;

            webBrowser1.Url = new Uri(Application.StartupPath + "\\BingMapsAJAX.html");

            dateTimePicker1.MinDate = DateTime.Now.AddDays(-7); //twitter is currently limited to previous 7 days
            dateTimePicker1.MaxDate = DateTime.Now.AddHours(12); //assume that the application might be open for several hours during a shift

            // Ensure it's only added once.
            DataGVResults.CellClick -= Row_Selected;
            DataGVResults.CellClick += Row_Selected;
            TxtBxAddress.Focus();

            //spdTweets = new SeattlePDTweetByBeat();
            //spdTweets.NewTweet += spdTweets_NewTweet;
        }

        /// <summary>
        /// Event handle the button click.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnHolla_Click(object sender, EventArgs e)
        {

            /*
            webBrowser1.Document.InvokeScript("addPin", new String[] { tacomaLatLon[0].ToString(), tacomaLatLon[1].ToString(), "username", "text", DateTime.Now.ToString() });
            return;
             */

            this.Cursor = System.Windows.Forms.Cursors.WaitCursor;

            try
            {
                string textBoxValue = TxtBxAddress.Text.Trim().ToUpper();
                if (e.GetType() == typeof(HtmlElementEventArgs))
                {
                    // came from our shift click?
                    lastLatLon = new double[] { Double.Parse(statusLabelTimer.Text.Split(',')[0]), 
                        Double.Parse(statusLabelTimer.Text.Split(',')[1]) };
                }
                else if (textBoxValue.Replace(" ", "") == "TACOMA,WA")
                {
                    lastLatLon = tacomaLatLon;
                }
                else if (textBoxValue == "<CENTER OF MAP>" || textBoxValue == "")
                {
                    string[] strLatLon = webBrowser1.Document.InvokeScript("getMapCenter", new String[] { }).ToString().Split(',');
                    lastLatLon = new Double[] { Double.Parse(strLatLon[0]), Double.Parse(strLatLon[1]) };
                }
                else
                {
                    //lastLatLon = TwitterUtil.ConvertAddress(TxtBxAddress.Text.Trim());
                    lastLatLon = BingGeoCodeAPI.GeocodeAddress(TxtBxAddress.Text.Trim());
                }
                if (lastLatLon == null)
                    throw new Exception();
            }
            catch (Exception ex)
            {
                this.Cursor = System.Windows.Forms.Cursors.Default;
                logger.Warn("Unable to geocode the address " + TxtBxAddress.Text.Trim());
                logger.Info(ex.ToString());
                //MessageBox.Show("Could not convert the address " + TxtBxAddress.Text.Trim() + " to a valid Latitude and Longitude value.", "Bad Address");
                statusLabelTimer.Text = "Could not convert " + TxtBxAddress.Text.Trim() + " to Lat/Long.";
                return;
            }

            SearchParameters currentParameters = new SearchParameters();
            currentParameters.Address = TxtBxAddress.Text.Trim();
            currentParameters.Latitude = lastLatLon[0];
            currentParameters.Longitude = lastLatLon[1];
            currentParameters.Radius = Math.Round((double)NumUDRadius.Value, 2);
            currentParameters.DateAndTime = DateTime.SpecifyKind(dateTimePicker1.Value, DateTimeKind.Local);
            currentParameters.MinutesBeforeAndAfter = Math.Round((double)NumUDMin.Value);

            if (!previousSearchList.Contains(currentParameters))
            {
                currentParameters.ID = previousSearchList.Count + 1;
                previousSearchList.Insert(0, currentParameters);
                previousSearchesToolStripMenuItem.DropDownItems.Clear();

                foreach (SearchParameters param in previousSearchList)
                {
                    previousSearchesToolStripMenuItem.DropDownItems.Add(param.ID.ToString() + " - " + param.Address + " | " + param.DateAndTime.ToShortDateString());
                }
            }

            this.Cursor = System.Windows.Forms.Cursors.Default;

            // What if all that changed was the time? Don't search again?
            theData = tUt.search(currentParameters);

            if (theData == null || theData.Count == 0)
            {
                logger.Info("No results.");
                MessageBox.Show("0 Posts Found!");
                return;
            }

            webBrowser1.Document.InvokeScript("clearMap", new String[] { });
            DataGVResults.DataSource = ToDataTableFiltered(theData, "", true);
            DGView_Other.DataSource = ToDataTableFiltered(theData, "", false);
            SendToMap(theData, "", lastLatLon);

            //Export data to a local csv file
            ExportToCSV.SaveToFile(theData);
        }
        /// <summary>
        /// This will allow you to turn the data into a table but filter out
        /// anything that does not match the given filter string.
        /// </summary>
        /// <typeparam name="T">The list type.</typeparam>
        /// <param name="data">The list of data objects.</param>
        /// <param name="filter">A string that will get lowercase'd and matched to all data.</param>
        /// <returns>A datatable that shows the tweets!</returns>
        public DataTable ToDataTableFiltered(List<SocialDataPoint> data, string filter, bool showGeoData)
        {
            if (data == null)
                return null;
            PropertyDescriptorCollection properties =
               TypeDescriptor.GetProperties(typeof(SocialDataPoint));
            DataTable table = new DataTable();

            /*foreach (PropertyDescriptor prop in properties)
                table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
            */
            table.Columns.Add("Text", typeof(String));
            table.Columns.Add("Time", typeof(DateTime));
            table.Columns.Add("User", typeof(String));
            table.Columns.Add("Source", typeof(String));
            foreach (SocialDataPoint item in data)
            {
                if (showGeoData && item.Lat.Equals(Double.NaN))
                    continue;
                else if (!showGeoData && !item.Lat.Equals(Double.NaN))
                    continue;
                // Matching the filter
                if (!Regex.IsMatch(item.Text.ToLower(), ".*" + filter.ToLower() + ".*"))
                    continue;
                DataRow row = table.NewRow();
                row["Text"] = item.Text;
                row["Time"] = item.BirthDate;
                row["User"] = item.User.Name + "(" + item.User.OtherInfo + ")";
                row["Source"] = item.Source.ToString().Substring(0, 2);
                table.Rows.Add(row);
            }
            if (showGeoData)
            {
                tabPage1.Text = String.Format(@"  Geo Tagged: {0}  ", table.Rows.Count);
            }
            else
            {
                tabPage2.Text = String.Format(@"  Other: {0}  ", table.Rows.Count);
            }

            DataGVResults.AutoResizeColumns();
            DGView_Other.AutoResizeColumns();

            logger.Debug(String.Format("Kept {0} of {1} data points based on time and filter criteria.", table.Rows.Count, data.Count));
            return table;
        }

        /// <summary>
        /// This method will iterate through the data and send each one to the web documents
        /// add pin method. It will use the filter string to filter out any data that should not
        /// be displayed. It will also pass along the lat lon at which the map should center.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="filter"></param>
        /// <param name="latlong"></param>
        public void SendToMap(List<SocialDataPoint> data, string filter, double[] latlong)
        {
            string zoomLevel = Convert.ToString(webBrowser1.Document.InvokeScript("getZoom", new String[] { }));
            webBrowser1.Document.InvokeScript("centerMap", new String[] { latlong[0].ToString(), latlong[1].ToString(), zoomLevel });

            //convert radius from miles to kilometers
            string radius = Convert.ToString(decimal.Multiply(NumUDRadius.Value, (decimal)1.609344));
            webBrowser1.Document.InvokeScript("drawCircle", new String[] { latlong[0].ToString(), latlong[1].ToString(), radius });
            int count = 0;
            foreach (SocialDataPoint item in data)
            {
                if (item.Lat.Equals(Double.NaN))
                    continue;
                if (!Regex.IsMatch(item.Text.ToLower(), ".*" + filter.ToLower() + ".*"))
                    continue;
                string username = "";
                if (item.User != null)
                {
                    if (item.User.Name != null && item.User.Name.Length != 0)
                        username = item.User.Name;
                    else
                        username = item.User.OtherInfo;
                }

                string description = item.Text;
                if (item.PictureUrl != null && item.PictureUrl.Length != 0)
                    description += "<HR>" + item.PictureUrl;

                //get the current zoom level
                //pass the zoom level back to the browser when you center it (or skip the 'center' function all together ???)
                webBrowser1.Document.InvokeScript("addPin", new String[] { item.Lat.ToString(), item.Lon.ToString(), username, description, item.BirthDate.ToString() });
                count++;
            }

            /* For Debugging */
            /*
            webBrowser1.Document.InvokeScript("addPin", new String[] { "47.2551307678223", "-122.441650390625", "user1", "Tacoma1", "3/3/2013 12:30 PM" });
            webBrowser1.Document.InvokeScript("addPin", new String[] { "47.2651307678223", "-122.451650390625", "user2", "Tacoma2", "3/3/2013 1:30 PM" });
            webBrowser1.Document.InvokeScript("addPin", new String[] { "47.2751307678223", "-122.461650390625", "user3", "Tacoma3", "3/3/2013 2:30 PM" });
            */

            logger.Debug("Push pins added to map=" + count);
        }

        //Sign up for events in the webbrowser control
        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            // Registering twice
            webBrowser1.Document.MouseUp -= Document_Click;
            webBrowser1.Document.MouseUp += Document_Click;

        }

        /// <summary>
        /// This method is a listener for clicks on the html document.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Document_Click(object sender, HtmlElementEventArgs e)
        {
            string lat = Convert.ToString(webBrowser1.Document.InvokeScript("getLat", new String[] { }));
            string lon = Convert.ToString(webBrowser1.Document.InvokeScript("getLon", new String[] { }));
            statusLabelTimer.Text = lat + "," + lon;
            logger.Debug("Retrieved data from click: " + lat + "," + lon);
            if (e.ShiftKeyPressed)
            {
                if (lat.Length == 0 || lon.Length == 0)
                    return;
                // Search.
                BtnHolla_Click(sender, e);
            }
        }

        void Row_Selected(object sender, EventArgs e)
        {
            webBrowser1.Document.InvokeScript("selectPin", new String[] { "" + ((DataGridViewCellEventArgs)e).RowIndex });
        }

        private void previousSearchesToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            foreach (SearchParameters param in previousSearchList)
            {
                if (e.ClickedItem.ToString().Equals(param.ID.ToString() + " - " + param.Address + " | " + param.DateAndTime.ToShortDateString()))
                {

                    TxtBxAddress.Text = param.Address;
                    NumUDRadius.Value = Convert.ToDecimal(param.Radius);
                    dateTimePicker1.Value = param.DateAndTime;
                    NumUDMin.Value = Convert.ToDecimal(param.MinutesBeforeAndAfter);

                    theData = tUt.getTweets(param.Latitude, param.Longitude,
                            param.Radius, param.DateAndTime, param.MinutesBeforeAndAfter,
                            new TwitterUtil.SocialMedium[] { TwitterUtil.SocialMedium.TWITTER });

                    if (theData == null || theData.Count == 0)
                    {
                        logger.Info("No results.");
                        MessageBox.Show("0 Posts Found!");
                        return;
                    }

                    webBrowser1.Document.InvokeScript("clearMap", new String[] { });

                    DataGVResults.DataSource = ToDataTableFiltered(theData, "", true);
                    DGView_Other.DataSource = ToDataTableFiltered(theData, "", false);
                    SendToMap(theData, "", new double[] { param.Latitude, param.Longitude });

                    return;
                }
            }
        }

        /// <summary>
        /// When you hit enter, things happen that you expect.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TxtBxAddress_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)
            {
                BtnHolla_Click(sender, e);
            }
        }

        private void DataGVResults_CellEnter(object sender, DataGridViewCellEventArgs e)
        {
            int index = e.RowIndex;
            if (index >= 0 && index < DataGVResults.RowCount)
            {
                webBrowser1.Document.InvokeScript("selectPin", new String[] { "" + index });
            }
        }

        private void toolStripAutoSearchChoice_Click(object sender, EventArgs e)
        {
            autoSearchOn = toolStripAutoSearchChoice.CheckState == CheckState.Checked;
            autoSearchSettingsToolStripMenuItem.Text = "Auto Search is " + (autoSearchOn ? "On" : "Off");
            resetAutoSearch();
        }

        private void toolStripIntervalChoice_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (toolStripIntervalChoice.SelectedItem.ToString().Contains("15"))
                autoSearchInterval = 15;
            else if (toolStripIntervalChoice.SelectedItem.ToString().Contains("30"))
                autoSearchInterval = 30;
            else if (toolStripIntervalChoice.SelectedItem.ToString().Contains("60"))
                autoSearchInterval = 60;
            resetAutoSearch();
        }

        private void resetAutoSearch()
        {
            autoSearchTimer.Enabled = autoSearchOn;
            autoSearchTimer.Stop();

            if (autoSearchOn)
            {
                autoSearchTimer.Interval = autoSearchInterval * 60000; //60000 = 1 minute, 1000 = 1 second
                autoSearchTimer.Start();

                statusLabelTimer.Text = "Next AutoSearch at " + DateTime.Now.AddMinutes((double)autoSearchInterval).ToShortTimeString();
            }
            else
                statusLabelTimer.Text = "";
        }

        private void autoSearchTimer_Tick(object sender, EventArgs e)
        {
            BtnHolla_Click(this, new EventArgs());
        }

        /// <summary>
        /// Hooks into the form shutting down so that we clean up nice and tight.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                tUt.closeStream();
                ExportToCSV.Dispose();
            }
            catch
            {
                //ignore for now
            }
        }

        /// <summary>
        /// Will filter the displayed results in both the gridview and webpage.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TBFilter_TextChanged(object sender, KeyPressEventArgs e)
        {
            if (lastLatLon != null && e.KeyChar == (char)13)
            {
                DataGVResults.DataSource = ToDataTableFiltered(theData, TBFilter.Text, true);
                DGView_Other.DataSource = ToDataTableFiltered(theData, TBFilter.Text, false);
                SendToMap(theData, TBFilter.Text, lastLatLon);
            }
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            webBrowser1.Document.InvokeScript("clearMap", new String[] { });
        }

        /// <summary>
        /// Toggles whether or not we are going to stream. Should shut down the stream when not in use.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void turnOnSeattlePDTweetByBeatToolStripMenuItem_Click(object sender, EventArgs e)
        {
            streamSeattleTweetByBeat = turnOnSeattlePDTweetByBeatToolStripMenuItem.CheckState == CheckState.Checked;
            streamingDataIsOffToolStripMenuItem.Text = "Streaming Data is " + (streamSeattleTweetByBeat ? "On" : "Off");

            if (streamSeattleTweetByBeat)
                tUt.init(spdTweets_NewTweet);
            else
                tUt.closeStream();
        }

        /// <summary>
        /// Ensures thread safe operations so that if someone is trying to reset the gui in anyway, we pass
        /// the operation off the the single thread that owns the gui.
        /// </summary>
        /// <param name="t"></param>
        /// <param name="e"></param>
        delegate void spdTweets_NewTweetCallback(object t, EventArgs e);
        private void spdTweets_NewTweet(object t, EventArgs e)
        {
            if (this.flowLayoutPanel1.InvokeRequired)
            {
                spdTweets_NewTweetCallback d = new spdTweets_NewTweetCallback(spdTweets_NewTweet);
                this.Invoke(d, new object[] { t, e });
                return;
            }
            Status tweet = (Status)t;
            SeattlePDTweetView newView = new SeattlePDTweetView(tweet.ScreenName,
                       tweet.User.Name,
                       tweet.User.ProfileImageUrl,
                       tweet.Text);
            newView.Click += newView_Click;
            flowLayoutPanel1.Controls.Add(newView);
            
            TxtBxAddress.Text = SeattlePDTweetByBeat.ParseAddress(newView.TweetText.Text);
            newView.ConvertedAddress = TxtBxAddress.Text;

            dateTimePicker1.Value = DateTime.Now;
            newView.CreatedAt = DateTime.Now;

            BtnHolla_Click(this, new EventArgs());
        }

        void newView_Click(object sender, EventArgs e)
        {
            SeattlePDTweetView spdTweet = (SeattlePDTweetView)sender;

            TxtBxAddress.Text = spdTweet.ConvertedAddress;
            dateTimePicker1.Value = spdTweet.CreatedAt;
            BtnHolla_Click(this, new EventArgs());
        }
    }
}
